#include "simu.h"

data_t* read(uint64* data_size)
{  
   double timestamp;
   long long unsigned int size;
   unsigned int data_elno;
   unsigned int i;   
   data_t* data;
   FILE* f;

   f = fopen("pkt_data", "r");

   if (!f)
   {
      puts("ERROR: Didn't find pkt_data file!\n");
      exit(-1);
   }

   fscanf(f, "%u", &data_elno);
   
   data = (data_t*)malloc(sizeof(data_t)*data_elno);

   for (i = 0; i < data_elno; i++)
   {
      fscanf(f, "%lf %llu", &timestamp, &size);
      data[i].timestamp = (uint64)(timestamp * TIME_PRECISION);
      data[i].size = size;
   }

   *data_size = (uint64)data_elno;

   fclose(f);

   return data;
}

/**
   initilize simulator data
   packet size and arrival interval are exponentially distribuited

   s: struct to initialize
   buffersize: size of queue buffer (in Bytes)
   pktint: packet arrival interval mean (miliseconds)
   pktsize: packet size mean (bytes)
   procspeed: packet processing speed (bytes/second)

   steps: number of simulation steps
   ignoredsteps: step number which data collection should start
 */
void simu_init(simu_t* s, uint64 buffersize, uint64 pktint, uint64 pktsize, uint64 procspeed,
               uint64 steps, uint64 ignoredsteps)
{
   // exponential dist mean is equal to 1/x where x is it's rate
   s->arrival_rate = 1.0 / (double)pktint;
   s->size_rate = 1.0 / (double)pktsize;

   s->data = read(&s->data_size);
   s->data_index = 0;
   s->procspeed = procspeed;
   s->buffer_size = buffersize;
   s->steps = steps;
   s->igsteps = ignoredsteps;
   s->clock = s->last_pkt_time = 0;
   s->queue = (pkt_t*)malloc(sizeof(pkt_t)*BUFFER_PKT_SIZE);
   s->next_free = 0;
   s->next_consume = 0;
   s->queue_elno = 0;
   s->queue_size = 0;

   if (!s->queue)
   {
      puts("No memory\n");
      exit(-1);
   }

   s->stats.pkt_arr_num = 0;
   s->stats.pkt_proc_num = 0;
   s->stats.pkt_arr_bytes = 0;
   s->stats.pkt_proc_bytes = 0;
   s->stats.stat_clock = 0;
   s->stats.mean_delay = 0;
   s->stats.queue_size = 0;
   s->stats.peek_num = 0;
   s->stats.pkt_dropped_num = 0;
}

void simu_destroy(simu_t* s)
{
   free(s->data);

   if (s->queue)
      free(s->queue);
}

void create_pkt(simu_t *s, pkt_t* p)
{
   data_t *d;

   if (s->data_index >= s->data_size)
   {
      fprintf(stderr, "Invalid new packet request\n");
      exit(1);
      return;
   }

   d = &(s->data[s->data_index++]);

   p->size = d->size;
   s->last_pkt_time = p->arrival_time = (uint64)d->timestamp;   
   p->procinit_time = p->procfini_time = 0;
   p->dropped = 0;
}

/**
   queues or drops pkt
 */
void enqueue(simu_t* s, pkt_t* p)
{
   if (s->queue_elno == BUFFER_PKT_SIZE || s->queue_size + p->size > s->buffer_size)
   {
      // queue full - drop pkt
      p->dropped = 1;
   }
   else
   {
      memcpy(s->queue + s->next_free, p, sizeof(pkt_t));
      s->next_free = (s->next_free + 1) % BUFFER_PKT_SIZE;
      s->queue_elno++;
      s->queue_size += p->size;
   }
}

// make sure queue is not empty before dequeuing
void dequeue(simu_t* s, pkt_t* p)
{
   if (s->queue_elno == 0)
   {
      fprintf(stderr, "Dequeue exception. Empty queue.\n");
      exit(1);
      return;
   }

   memcpy(p, s->queue + s->next_consume, sizeof(pkt_t));
   s->next_consume = (s->next_consume + 1) % BUFFER_PKT_SIZE;
   s->queue_elno--;
   s->queue_size -= p->size;
}

void bookmark(simu_t* s, pkt_t* p, etype t)
{
   uint64 delay;
   double proc_n;

   if (s->curstep < s->igsteps)
      return;
   else if (s->stats.stat_clock == 0)
      s->stats.stat_clock = s->clock;

   if (t == ETYPE_PKT_PROCESSED)
   {
      delay = p->procfini_time - p->arrival_time;
      proc_n = (double)s->stats.pkt_proc_num;

      s->stats.mean_delay = ( s->stats.mean_delay ) * ( proc_n  / ( proc_n + 1 ) ) +
         ( delay ) / ( proc_n + 1 );
      s->stats.pkt_proc_num++; 
      s->stats.pkt_proc_bytes += p->size;
   }
   else if (t == ETYPE_PKT_ARRIVED)
   {      
      if (s->stats.pkt_arr_num % MEASURE_INTERVAL == 0)
      {
         s->stats.queue_size += s->queue_size;
         s->stats.peek_num++;
      }

      s->stats.pkt_arr_num++;
      s->stats.pkt_arr_bytes += p->size;
   }
   else if (t == ETYPE_PKT_DROPPED)
   {
      // ignore for now
      s->stats.pkt_dropped_num++;
   }
}

/**
   start simulator with data
 */
void simu_start(simu_t* s)
{
   pkt_t p_new, p_proc;
   int i;
   uint64 next_event; /* time when next pkt will have its processing finished */

   rnd_init();

   // first iteration
   // PKT ARRIVED
   create_pkt(s, &p_proc);
   s->clock = p_proc.arrival_time;
   p_proc.procinit_time = s->clock;
   next_event = s->clock + PROC_TIME(p_proc.size, s->procspeed);
   bookmark(s, &p_proc, ETYPE_PKT_ARRIVED);

   // prepare next pkt
   create_pkt(s, &p_new);     

   // do while there are packets to be processed
   for ( i = 0; s->data_index < s->data_size ; i++ )
   {
      // what happens first? does our p_proc pkt is processed
      // or a new pkt arrives?
      s->curstep = i;

      if (p_new.arrival_time < next_event)
      {
         // PKT ARRIVED

         s->clock = p_new.arrival_time;

         bookmark(s, &p_new, ETYPE_PKT_ARRIVED);

         // pkt arrives before processing finished       

         // add it to the queue
         enqueue(s, &p_new);

         if (p_new.dropped)
         {
            // PKT DROPPED
            bookmark(s, &p_new, ETYPE_PKT_DROPPED);
         }
         else
         {
            // PKT ENQUEUED
         }

         // prepare new pkt for next iteration
         create_pkt(s, &p_new);
      }
      else
      {
         // PKT PROCESSED

         s->clock = next_event;

         // a processed pkt has finished
         p_proc.procfini_time = s->clock;

         bookmark(s, &p_proc, ETYPE_PKT_PROCESSED);

         // get new pkt from queue, if any
         if (s->queue_elno)
         {
            // PKT DEQUEUED

            dequeue(s, &p_proc);
         }
         else
         {
            // PKT ARRIVED

            // just wait for new pkt
            p_proc = p_new;
            s->clock = p_proc.arrival_time;

            bookmark(s, &p_proc, ETYPE_PKT_ARRIVED);

            // prepare new pkt for next iterarion
            create_pkt(s, &p_new);
         }

         // set start process time
         p_proc.procinit_time = s->clock;
         // calculate next event
         next_event = s->clock + PROC_TIME(p_proc.size, s->procspeed);
      }
   }

}

uint64 get_args(int argn, char** argv)
{
   uint64 procspeed;

   if (argn != 2)
   {
      fprintf(stderr, "#Using default %llu B\\s processing speed\n", PROCSPEED);
      return PROCSPEED;
   }
   
   procspeed = (uint64)atoll(argv[1]);

   fprintf(stderr, "#Using %llu B\\s processing speed\n", procspeed);

   return procspeed;
}

int main(int argn, char** argv)
{
   simu_t s;
   double utilization, mean_queue_size;

   simu_init(&s, BUFFERSIZE,  /* buffer size */
             0, /* there will be a file with packet info*/
             0, /* there will be a file with packet info*/
             get_args(argn, argv), /* (B/s processing speed) */
             0, /* there will be a file with packet info*/
             10000);  /* ignored packets */


   simu_start(&s);

   utilization = ( s.stats.pkt_arr_bytes / ( (double)s.clock - s.stats.stat_clock ) * (double)TIME_PRECISION ) / ( (double)s.procspeed  );

   mean_queue_size = (double)s.stats.queue_size / (double)s.stats.peek_num;

   puts("#Utilization\tMean queue size(B)");

   printf("%lf\t%lf\n", utilization, mean_queue_size);

   simu_destroy(&s);

   return 0;
}
